Auteur : Gaspard Jeannerot
Temps de réalisation :
Sur temps libre entre Février - Juin 2020
Objectif :
Dans un premier temps, exploiter des données gouvernementales afin de montrer des tendances, et/ou des évolutions potentielles concernant les crimes et les délits en France. Dans un second temps, développer plusieurs algorithmes d'apprentissage automatique de manière empirique pour prévoir l'apparition des crimes et délits en France.
Après avoir visionné plusieurs vidéos (première, deuxième) concernant la crise des migrants en Europe, j'ai pris conscience que le monde dans lequel nous vivions pouvait très rapidement évoluer. Pire, en essayant de chercher à comprendre ce phénomène et de trouver des causes et des raisons, j'ai rapidement compris qu'un individu comme moi était incapable de pouvoir arranger les choses.
Étant une personne appréciant une vie stable, j'ai essayé de déterminer si "cela" pouvait un jour m'arriver. Puis en prenant du recul sur cette situation, une question m'est survenue : suis je en danger là où j'habite ?
Afin d'apporter des éléments réalistes et concrets, j'ai essayé de chercher quels pouvaient être les facteurs pouvant me mener à une situation ou à un environnement dangereux. Mais avant cela, il m'a fallu comprendre et définir comment évaluer des danger. Dans ce travail, je le définis comme tout acte humain volontaire mettant en péril la sureté (ou stabilité) et pouvant entrainer ou exposer mes proches, mon entourage, à des conséquences préjudiciables.
Pour factuellement déterminer ce "danger", j'ai réfléchi à comment mesurer les actes nuisibles à mon échelle et la réponse a été simple : en analysant les crimes et les délits enregistrés dans mon pays.
1. Préparation des données pour l'analyse
1.1. Importations des outils
1.2. Importations du Jeu de donnée
1.3. Transposition des données
1.4. Gestion multiple des feuilles
1.5. Récupération du champ date
1.6. Fixation des types de données
1.7. Colonnes inutilisées
2. Analyse du jeu de données
2.1. France Métropolitaine
2.2. Crimes et délit par département
2.3. Analyse focalisée
2.3.1. Cambriolage
2.3.1. Homicides
2.3.1. Vols
3. Machine Learning
3.1. Cas d’étude
3.2. Industrialisation du processus de prédiction
3.3. Analyse focalisée
3.4. Prédiction
4. Synthèse
4.1. Conclusion (SWOT)
4.2. Bilan
Source :
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
sns.set(color_codes=True)
### Generique variables definition to import
source = 'source/'
xl_misdemeanours_crimes_path = source + 'tableaux-4001-ts.xlsx'
xl_population_path = source + 'estim-pop-dep-sexe-gca-1975-2020.xls'
### Excel file import
crime_xl_data = pd.ExcelFile(xl_misdemeanours_crimes_path)
population_xl_data = pd.ExcelFile(xl_population_path)
### Get a look on sheet pages in the excel
print(crime_xl_data.sheet_names)
Le jeu de donnée est composé de plusieurs pages qui peuvent être traitées de manière indépendante. Nous allons nous focaliser sur la feuille "France métropolitaine".
# Define France metropolitaine
current_sheet = crime_xl_data.sheet_names[1]
# Read one sheet to begin ('France_Métro') --> libellée index to write minus two lines
df = pd.read_excel(crime_xl_data, current_sheet, index_col = 'libellé index')
# Examine the head of the DataFrame
df.head(3)
Le jeu de donnée possède des données temporelles en tant que dimensions. Afin de rendre les variables plus exploitables, nous allons opérer à une transformation de transposition. De plus afin de pouvoir faciliter la visualisation de nos prochains graphiques, nous allons aussi en profiter pour dissocier le champ date en années et mois et réindexer le jeu de donnée.
# Drop init 'index' columns, and Transpose Data
df = df.drop(columns=['Index'])
df = df.T
df.head()
Une fois les données transposées, nous pouvons facilement nous rendre compte que nous avons un problème de typage concernant l'index. Elle n'est pas considérée comme un type datetime. Corrigeons cela. Il va aussi falloir tenir compte du document original et des multiples pages.
df_all = pd.DataFrame(columns=df.columns.values)
df_all['departement'] = np.nan
all_name_sheet = crime_xl_data.sheet_names
invalid_sheet = ['France_Entière','France_Métro']
all_name_sheet = [e for e in all_name_sheet if e not in invalid_sheet]
for i in all_name_sheet:
df_tmp = pd.read_excel(crime_xl_data, sheet_name=str(i), index_col = 'libellé index')
df_tmp = df_tmp.drop(columns=['Index'])
df_tmp = df_tmp.T
df_tmp['departement'] = str(i)
df_all = df_all.append(df_tmp)
df_all.departement = df_all.departement.astype(str)
df_all.head()
# Sur le jeu de donnée France métropolitaine
# Reset index, Get Year, Get Month
df = df.reset_index()
df['year'] = df['index'].astype(str).str[0:4]
df['month'] = df['index'].astype(str).str[5:7]
# Define new date column
combined = df.year.str.cat(df.month,sep=' ')
df['date'] = pd.to_datetime(combined)
# Drop 'index' column and set new index on date
df = df.drop(columns='index')
df = df.set_index('date')
df.head()
# Sur le jeu de donnée complet
# Reset index, Get Year, Get Month
df_all = df_all.reset_index()
df_all['year'] = df_all['index'].astype(str).str[0:4]
df_all['month'] = df_all['index'].astype(str).str[5:7]
# Define new date column
combined = df_all.year.str.cat(df_all.month,sep=' ')
df_all['date'] = pd.to_datetime(combined)
# Drop 'index' column and set new index on date
df_all = df_all.drop(columns='index')
df_all = df_all.set_index('date')
df_all.head()
Le champ date est maintenant l'index. Voyons maintenant les types de données.
Regardons si plusieurs colonnes ne possèderaient pas un typage non conventionnel ou potentiellement inapproprié pour notre analyse.
df.dtypes.values
df.dtypes
Fixons les types des champs 'year' et 'month'
df.year = df.year.astype(str).astype(int)
df.month = df.month.astype(str)
En regardant de plus près, nous pouvons constater que plusieurs colonnes se nomment 'index non utilisé', penchons-nous dessus.
# Define names of index and columns
df.index.names = ['index']
df.columns.names = ['libellé columns']
df['Index non utilisé'].sum()
En visualisant ces résultats, nous pouvons constater que ces dimensions nommées 'Index non utilisé' ne représentent pas une grande quantité d'information. Aussi nous n'allons pas en tenir compte.
print(df.shape)
df_all = df_all.drop(columns='Index non utilisé')
df = df.drop(columns='Index non utilisé')
print(df.shape)
Les manipulations de bases ont été réalisées, nous pouvons donc démarrer notre analyse.
Intéressons-nous au jeu de données du point de vue macro.
# Examine the shape of the DataFrame
print(df.shape)
# Count the number of missing values in each column
print(df.isnull().sum().values)
Le jeu de donnée est composé de 289 observations pour 105 dimensions. Aucune valeur null ou manquante n’est répertoriée. C'est bien, nous n'allons pas avoir besoin d'effectuer une manipulation de traitements les concernant.
Le jeu de donnée dispose de libellés très explicites. Les voici :
print(df.columns.values)
Ces informations nous permettent de comprendre que ce jeu de donnée regroupe principalement des incidents (crimes ou délits) agrégés par mois d'activité. Aussi pour améliorer notre étude, nous renforçons-le en ajoutant des données concernant la population française.
Le jeu de donnée population_xl_data est une estimation de la population française diversifiée par les départements, les années ainsi que le sexe des individus. Il va nous permettre par croisement d'apporter des indications complémentaires sur les actes criminels en France.
# Chargement de XL en Dataframe
df_population = pd.read_excel(population_xl_data, '2020', header = 4, index='Départements')
# Nettoyage du jeu de données
df_population.rename(columns={'Unnamed: 0':'Departement','Total.1':'Total Homme','Total.2':'Total Femme'}, inplace=True)
# Suppresion des lignes indésirables
df_population = df_population.drop([96, 102, 103, 104, 105])
valid_population_columns = ['Departement', 'Total','Total Homme','Total Femme']
# Finition sur le DataFrame
df_population = df_population[valid_population_columns]
df_population.head()
total_population_france_metropolitaine = df_population.loc[:96,'Total'].sum()
total_population_dom = df_population.loc[96:,'Total'].sum()
total_population_france_metropolitaine_dom = df_population.loc[:,'Total'].sum()
# En 2020
print("La population de la France métropolitaine est estimée à " + str(int(total_population_france_metropolitaine)) + " individus.")
print("La population des DOMs est estimée à " + str(int(total_population_dom)) + " individus.")
print("La population de la France métropolitaine et des DOMs est estimée à " + str(int(total_population_france_metropolitaine_dom)) + " individus.")
df_population['% Population Métropolitaine'] = round(df_population.Total / total_population_france_metropolitaine_dom * 100, 2)
print("Vérification % data : " + str(round(df_population['% Population Métropolitaine'].sum(), 2)))
df_population.head()
Nous disposons maintenant d'un second dataframe nous permettant de connaitre la part de la population pour chaque département. Reprenons l'analyse de notre jeu de donnée initial.
round(df.describe(), 2)
L'analyse des corrélations d'un jeu de donnée est toujours intéressante. Elle permet généralement de distinguer assez facilement quelles sont les variables pour lesquelles, il existe des corrélations positives ou négatives. Toutefois, il est toujours bon de rappeler que corrélation ne signifie pas causalité.
corr = df.corr()
corr.style.background_gradient(cmap='coolwarm').set_precision(2)
L'analyse de cette matrice de corrélation est assez lourde. Nous pouvons voir de nombreuses corrélations positives et négatives sur différentes variables. Certaines peuvent sembler être liées, par exemple les "Sequestrations" sont très fortement corrélées avec les "Tentatives homicides pour d'autres motifs" ou encore les "Viols sur des majeur(e)s". Tandis que d'autres paraissent surprenantes, "Destructions, cruautés et autres délits envers les animaux" est aussi très fortement corrélé avec les "Violations de domicile".
# Vision globale
plt.matshow(df.corr())
plt.show()
En regardant un point de vue global, nous pouvons facilement constater que des "clusters" de corrélation positive et négative semblent se dégager de cette matrice. En nous y intéressant de plus près, nous pouvons constater qu'une partie de ces cluster varie conjointement et nous pouvons supposer que ces variations peuvent dépendre de périodes temporelles tels que les saisons ou les mois encore.
column_to_analyse = df_all.columns.values
# Enlever le département dans les colones à analyse
# Delete num departement
column_to_analyse = np.delete(column_to_analyse,-3)
# delete month
column_to_analyse = np.delete(column_to_analyse,-1)
# delte year
column_to_sum = np.delete(column_to_analyse,-1)
df_france=df.loc[:,column_to_sum]
df_france["Total Incident"] = df_france[column_to_sum].sum(axis=1)
df_france = df_france.drop(columns=column_to_sum)
df_france.head(5)
fig, ax = plt.subplots(figsize = (10, 6))
sns.lineplot(data=df_france, ax=ax, ci=int, linewidth=1);
Le nombre total d'incident à chuter début 2020. Dans l'ensemble, la description des données de manière gloable est difficile à interpréter. Aussi nous allons nous focaliser sur plusieurs axes clés. Mais avant regardons comment se comportent ces crimes et délits par département.
Regardons la part des crimes et des délits vis-à-vis de leurs zones géographiques. Pour cela, nous allons consolider notre jeu de données avec les informations correspondantes.
column_to_analyse = df_all.columns.values
# Enlever le département dans les colones à analyse
# Delete num departement
column_to_analyse = np.delete(column_to_analyse,-3)
# delete month
column_to_analyse = np.delete(column_to_analyse,-1)
# Validation du typage dans ce df
df_all[column_to_analyse] = df_all[column_to_analyse].astype(int)
df_all.departement = df_all.departement.astype(str)
df_all['total_incidents'] = df_all[column_to_analyse].sum(axis=1)
df_all.head()
Le jeu de donnée est maintenant utilisable. Regardons une description du nombre de crime et délit commis par département.
df_all.groupby('departement').total_incidents.describe().head()
mean = round((df_all.groupby('departement').total_incidents.mean().values).mean(),2)
print("Moyenne : " + str(mean))
print("Moyenne x 3 : " + str(mean*3))
print("Moyenne / 3 : " + str(mean/3))
Depuis 1996, nous pouvons constater qu'en France par département et par mois, il y a en moyenne 4945 incidents.
Les limites supérieures à notre analyse vont être égales à 3 fois plus et 3 fois moins la valeur moyenne. Elles correspondent ainsi à 14 835 (limite haute) et 1 648 (limite basse) d'incidents.
moyenne = round(df_all.groupby('departement').total_incidents.mean(), 2).values
departement = df_all.groupby('departement').total_incidents.mean().keys()
df_mean_departement = pd.DataFrame({'Moyenne':moyenne})
df_mean_departement = df_mean_departement.set_index(departement)
print("Moyenne d'acte criminel/délit par mois par département")
df_mean_departement = df_mean_departement.sort_values(by='Moyenne', ascending=True)
print(df_mean_departement.head())
df_mean_departement = df_mean_departement.sort_values(by='Moyenne', ascending=False)
print(df_mean_departement.head())
print("Departement avec nb incidents supérieur à la limite haute : ")
print(df_mean_departement[df_mean_departement['Moyenne'] > (mean*3)])
print("Departement avec nb incidents inférieur à la limite basse : ")
print(df_mean_departement[df_mean_departement['Moyenne'] < (mean/3)].values)
# Nombre d'incidents total en fm sur l'année 2019
year = df_all.groupby('year').total_incidents.sum()
print("\nNombre d'incidents sur l'année 2019 : " + str(year[2019]))
# Nombre d'incidents total par département sur l'année 2019
year = df_all.groupby(['year','departement']).total_incidents.sum()
year[2019].head()
Au vu du nombre d'incidents constaté, nous pouvons supposé que les 3 départements 75, 13 et 59 peuvent être assimilé à des zones plus à risques car avec un nombre d'incidents supérieur à la limite du nombre d'incidents préétabli. Mais pour avoir une vision plus réalisée, regardons le pourcentage d'incident par individu (à savoir par habitant) en fonction des départements.
df_tmp = df_population.merge(year[2019], left_on='Departement', right_on='departement')
df_tmp.head()
df_tmp["% Incident / Personne"] = round(df_tmp["total_incidents"] / df_tmp["Total"] * 100, 2)
df_tmp.head()
df_tmp = df_tmp.sort_values(by='% Incident / Personne', ascending=True)
df_tmp.head(10)
df_tmp = df_tmp.sort_values(by='% Incident / Personne', ascending=False)
df_tmp.head(10)
En réalisant ces quelques transformations, nous disposons maintenant du taux en pourcentage théorique du nombre d'incidents par personne en fonction des départements. Ce pourcentage permet de visualiser en fonction du nombre de personnes les départements les plus enclins à être source d'incidents. Toutefois, ces chiffres ne prennent pas en compte des cas réalistes où par exemple un individu serait à l'origine de plusieurs incidents.
Afin de faciliter l'analyse de ce jeu de donnée, définissons plusieurs pistes d'actes criminels dans notre évaluation. Nous allons étudier les cambriolages, les homicides et les vols.
Commençons par l'axe cambriolage. Reprenons donc la partie description élémentaire précédemment vue.
# Crimes and mesdemeanours concerning burglary
cambriolage_columns = df.filter(regex=(".*[Cc]ambriolage?s.*")).columns.values
round(df[cambriolage_columns].describe(), 2)
Pour que cela soit plus représentatif, visualisation le boxplot associé.
fig, ax = plt.subplots(figsize = (10, 6))
sns.boxplot(x="libellé columns", y="value", data=pd.melt(df[cambriolage_columns]))
ax.set_xticklabels(ax.get_xticklabels(), rotation=45, horizontalalignment='right');
Il est clair que les cambriolages des résidences principales occupent la major partie des cambriolages en France.
fig, ax = plt.subplots(figsize = (10, 6))
sns.lineplot(data=df.loc[:,cambriolage_columns], ax=ax, ci=int, linewidth=1);
Une tendance semble émerger depuis 2012 pour les cambriolages d'habitations principales. Aussi réduisons la période à partir de cette année.
fig, ax = plt.subplots(figsize = (10, 6))
sns.lineplot(data=df.loc[:'2012-01-01',cambriolage_columns], ax=ax, linewidth=1);
La tendance est bien confirmée. Maintenant, regardons quand le pic de cambriolage des résidences principales se produit. Comparons aussi les cambriolages de l'année 2019 avec la moyenne des cambriolages depuis 1996. Par an, combien avons nous en moyenne de cambriolage ?
print(round(df[cambriolage_columns].mean(),0))
fig, ax = plt.subplots(figsize = (10, 6))
sns.lineplot(data=df.loc['2020-01-01':'2018-01-01',cambriolage_columns], ax=ax, linewidth=2)
for mean in df[cambriolage_columns].mean().values:
ax.axhline(mean, ls='-', color='black', linewidth=1)
Nous pouvons constater que seulement les résidences principales sont concernées par une augmentation du nombre de cambriolages vis-à-vis de leur moyenne depuis 1996. De plus les pics de cambriolages ne concernent visuellement que les habitations principales. Ces pics semblent être focalisé sur les mois de novembre décembre, soit peu de temps avec Noël. Supposons que les autres types de cambriolages soient négligeables, regroupons donc pour continuer notre analyse.
df_cambriolage=df.loc[:,cambriolage_columns]
other_cambriolage_columns = [
'Cambriolages de résidences secondaires',
'Cambriolages de locaux industriels, commerciaux ou financiers',
"Cambriolages d'autres lieux"
]
df_cambriolage["Autre"] = df_cambriolage[cambriolage_columns].sum(axis=1)
df_cambriolage = df_cambriolage.drop(columns=other_cambriolage_columns)
print(df_cambriolage.head(5))
fig, ax = plt.subplots(figsize = (8, 5))
sns.boxplot(x="libellé columns", y="value", data=pd.melt(df_cambriolage));
En les regroupant, nous pouvons constater que les cambriolages de lieux n'étant pas des habitations principales représentent une part plus significative du nombre de cambriolages. Intéressons-nous maintenant au graphique temporel associé.
fig, ax = plt.subplots(figsize = (10, 6))
sns.lineplot(data=df_cambriolage, ax=ax, ci=int, linewidth=1);
En les visualisant, nous pouvons constater que la tendance remarquée sur les pics de cambriolages sur les résidences principales est aussi représentée pour les cambriolages d'autres établissements. Nous pouvons en déduire qu'il y a une relation de corrélation entre depuis 2012 entre ces deux variables. Toutefois, il nous est impossible pour l'instant d'en déduire s'il s'agit d'une corrélation hasardeuse ou d'une relation de causalité.
Est-ce que les cambriolages sont représentés dans le département possédant le plus d'incidents ?
Cette question peut être répondue de deux manières. Soit par l'intermédiaire de nombre d'incidents vis-à-vis des départements. Ou alors par le taux d'incident par personne par département. Ces deux approches se valent aussi reprenons les chiffres précédemment calculer.
Pour rappel, le département avec le plus d'incidents est le 75 avec une moyenne de 24 465 incidents par mois. Et le département 48 est celui avec le taux d'incident par personne le plus élevé, à savoir 35.04 % incident par personne.
# Restriction sur le département 75
df_cambriolage_2 = df_all.loc[df_all.departement == '75',cambriolage_columns]
df_cambriolage_2["Autre"] = df_cambriolage_2[cambriolage_columns].sum(axis=1)
df_cambriolage_2 = df_cambriolage_2.drop(columns=other_cambriolage_columns)
df_cambriolage_2.head()
fig, ax = plt.subplots(figsize = (10, 6))
sns.lineplot(data=df_all.loc[df_all.departement == '75',cambriolage_columns], ax=ax, ci=int, linewidth=1);
En nous intéressant à ce graphique nous pouvons constater que le nombre de cambriolages dans ce département suit une descendante depuis 1996. Cependant depuis 2009-2010, cette tendance c'est inversé en ayant un pic courant 2013. Focalisons nous maintenant le département avec le taux d'incident le plus élevé.
df_cambriolage_3 = df_all.loc[df_all.departement == '48',cambriolage_columns]
df_cambriolage_3["Autre"] = df_cambriolage_3[cambriolage_columns].sum(axis=1)
df_cambriolage_3 = df_cambriolage_3.drop(columns=other_cambriolage_columns)
df_cambriolage_3.head()
fig, ax = plt.subplots(figsize = (10, 6))
sns.lineplot(data=df_all.loc[df_all.departement == '49',cambriolage_columns], ax=ax, ci=int, linewidth=1);
Malgré un taux relativement élevé d'incident par personne, ce département ne possède pas un grand nombre d'observations concernant les cambriolages. Pour des raisons des granularités, il n'est pas intéressant d'étudier par comparaison les départements 48 et 75. Toutefois, regardons la part de cambriolage par personne et par département.
df_cambriolage = df_all.loc[:,cambriolage_columns]
df_cambriolage["Total cambriolage"] = df_cambriolage.loc[:,cambriolage_columns].sum(axis=1)
df_cambriolage["departement"] = df_all.loc[:,'departement']
df_cambriolage["year"] = df_all.loc[:,'year']
# df_cambriolage.head()
mean_cambriolage = round(df_cambriolage.groupby('departement')["Total cambriolage"].mean(), 2).values
df_cambriolage_mean_departement = pd.DataFrame({'Moyenne':mean_cambriolage})
df_cambriolage_mean_departement = df_cambriolage_mean_departement.set_index(departement)
print("Nombre de cambriolage moyen par département par mois : " + str())
df_cambriolage_mean_departement = df_cambriolage_mean_departement.sort_values(by='Moyenne', ascending=True)
print(df_cambriolage_mean_departement.head())
df_cambriolage_mean_departement = df_cambriolage_mean_departement.sort_values(by='Moyenne', ascending=False)
print(df_cambriolage_mean_departement.head())
df_cambriolage_year = df_cambriolage.groupby(['year','departement'])["Total cambriolage"].sum()
df_cambriolage_personne = df_population.merge(df_cambriolage_year[2019], left_on='Departement', right_on='departement')
df_cambriolage_personne["% Cambriolage / Personne"] = round(df_cambriolage_personne["Total cambriolage"] / df_cambriolage_personne["Total"] * 100, 2)
df_cambriolage_personne.head()
df_cambriolage_personne = df_cambriolage_personne.sort_values(by='% Cambriolage / Personne', ascending=True)
print("Les plus faibles taux de % de cambriolage / personne")
df_cambriolage_personne.head()
df_cambriolage_personne = df_cambriolage_personne.sort_values(by='% Cambriolage / Personne', ascending=False)
print("Les plus forts taux de % de cambriolage / personne")
df_cambriolage_personne.head()
print("Moyenne d'acte criminel/délit par mois par département")
Pour résumer l'analyse des cambriolages en France :
Continuons avec l'axe homicide.
# Crimes and mesdemeanours concerning murders
homicides_columns = df.filter(regex=(".*[Hh]omicide?s.*")).columns.values
fig, ax = plt.subplots(figsize = (10, 6))
sns.boxplot(x="libellé columns", y="value", data=pd.melt(df[homicides_columns]))
ax.set_xticklabels(ax.get_xticklabels(), rotation=45, horizontalalignment='right')
round(df[homicides_columns].describe(), 2)
Cet axe regroupe deux aspects. D'un côté les homicides et de l'autre les tentatives d'homicide. On remarque que l'immense majorité de ces cas représente des homicides et tentatives d'homicide pour des raisons autres que "pour voler", "à l'occasion de vols" ou "contre des enfants de moins de 15 ans".
fig, ax = plt.subplots(figsize = (10, 6))
sns.lineplot(data=df[homicides_columns], ax=ax, ci=int, linewidth=1);
Cette visualisation est difficile à interpréter, pour plus de simplicité nous allons décomposer en homicide et tentative d'homicides. Cependant, nous pouvons constater une tendance émergente pour les tentatives d'homicide "d'autres motifs" depuis 2010. De plus, deux pics d'homicides sont représentés autour de l'année 2016.
# Homicides
only_homicides_columns = df.filter(regex=(".*Homicides.*")).columns.values
fig, ax = plt.subplots(figsize = (10, 6))
sns.lineplot(data=df[only_homicides_columns], ax=ax, ci=int, linewidth=1)
# Tentatives
only_tentative_homicides_columns = df.filter(regex=(".*homicides.*")).columns.values
fig, ax = plt.subplots(figsize = (10, 6))
sns.lineplot(data=df[only_tentative_homicides_columns], ax=ax, ci=int, linewidth=1)
# Zoom
fig, ax = plt.subplots(figsize = (10, 6))
sns.lineplot(data=df.loc['2016':'2014',homicides_columns], ax=ax, linewidth=1);
La tendance et ces pics sont plus clairs maintenant. En affichant la vue entre l'année 2014 et 2016, nous pouvons observer les deux pics d'homicides précédemment évoquées en fin d'année 2015 et en été 2016. Ces pics correspondent aux deux périodes d'attaques terroristes en France. La première à Paris sur plusieurs sites (dont le bataclan) et la seconde à Nice sur la promenade des Anglais. Ce deuxième justifie aussi le pic de tentatives d'homicide dû au grand nombre de blessés par cette seconde attaque.
Le '75' est le département avec le plus haut taux de cambriolage. Quand est-il des homicides ?
# Restriction sur le département 75
df_homicide = df_all.loc[df_all.departement == '75',homicides_columns]
df_homicide["All Homicide"] = df_homicide[only_homicides_columns].sum(axis=1)
df_homicide["All Tenta Homicide"] = df_homicide[only_tentative_homicides_columns].sum(axis=1)
df_homicide = df_homicide.drop(columns=homicides_columns)
fig, ax = plt.subplots(figsize = (10, 6))
sns.lineplot(data=df_all.loc[df_all.departement == '75',homicides_columns], ax=ax, ci=int, linewidth=1)
fig, ax = plt.subplots(figsize = (10, 6))
sns.lineplot(data=df_homicide, ax=ax, ci=int, linewidth=1)
df_homicide.head()
Le département 75 a fait partie zones d'attaque terroriste, d'où la présence du pic fin 2015. En comparaison de la France, ce département est l'une des principales zones où l'on peut constater des homicides et tentatives d'homicide. De plus, ce département présente une hausse du nombre de tentatives d'homicide.
# Restriction sur le département 48
df_homicide = df_all.loc[df_all.departement == '48',homicides_columns]
df_homicide["Autre"] = df_homicide[homicides_columns].sum(axis=1)
df_homicide = df_homicide.drop(columns=homicides_columns)
fig, ax = plt.subplots(figsize = (10, 6))
sns.lineplot(data=df_all.loc[df_all.departement == '48',homicides_columns], ax=ax, ci=int, linewidth=1);
Dans le département 48, nous constatons une absence totale d'homicide et un très faible taux du nombre de tentatives d'homicide. Pourraient être intéressants de regarder les zones les plus soumis aux homicides et tentatives d'homicide.
Pour résumer l'axe homicide :
Passons maintenant à l'axe vols.
# Crimes and mesdemeanours concerning thief
voles_columns = df.filter(regex=(".*[Vv]ol?[es].*")).columns.values
fig, ax = plt.subplots(figsize = (10, 6))
sns.boxplot(x="libellé columns", y="value", data=pd.melt(df[voles_columns]))
ax.set_xticklabels(ax.get_xticklabels(), rotation=45, horizontalalignment='right');
L'analyse de l'axe vols est complexe. Pour permettre de la rendre plus pertinente, nous allons la décomposer en sous-axes.
La grande quantité d'information rend difficile l'exploitation simple de ce dernier axe. Aussi nous allons le subdivisé en fonction de critère pour avoir 5 sous catégories :
vols_with_hands_column = df.filter(regex=(".*Vols à main.*")).columns.values
fig, ax = plt.subplots(figsize = (10, 6))
sns.boxplot(x="libellé columns", y="value", data=pd.melt(df[vols_with_hands_column]))
ax.set_xticklabels(ax.get_xticklabels(), rotation=45, horizontalalignment='right');
fig, ax = plt.subplots(figsize = (10, 6))
sns.lineplot(data=df[vols_with_hands_column], ax=ax, ci=int, linewidth=1);
fig, ax = plt.subplots(figsize = (10, 6))
sns.lineplot(data=df.loc['2019':'2017',vols_with_hands_column], ax=ax, ci=int, linewidth=1);
Il apparait très clairement une tendance périodique sur les vols à main armée contre des établissements industriels ou commerciaux. Celle-ci semble correspondre à l'arrivée de la période de Noël. Les vols à main armée contre des établissements industriels ou commerciaux suivent aussi une tendance qui tend à diminuer depuis 2010. Sur cet axe, les vols à main armée contre des établissements industriels ou commerciaux sont les plus importants, les autres étaient moins importants, aussi nous allons les regrouper.
df_vols_with_hands = df.loc[:,vols_with_hands_column]
other_steal_columns = [
'Vols à main armée contre des établissements financiers',
'Vols à main armée contre des entreprises de transports de fonds',
'Vols à main armée contre des particuliers à leur domicile'
]
df_vols_with_hands["Autre"] = df_vols_with_hands[other_steal_columns].sum(axis=1)
df_vols_with_hands = df_vols_with_hands.drop(columns=other_steal_columns)
fig, ax = plt.subplots(figsize = (8, 5))
sns.boxplot(x="libellé columns", y="value", data=pd.melt(df_vols_with_hands));
fig, ax = plt.subplots(figsize = (10, 6))
sns.lineplot(data=df_vols_with_hands, ax=ax, ci=int, linewidth=1);
df_vols_with_hands.head()
Cette représentation plus simple nous confirme qu'il apparait ici très clairement qu'en France les vols à main armée tendent à diminuer.
vols_specific_column = ['Vols à la tire', "Vols à l'étalage", 'Vols à la roulotte']
fig, ax = plt.subplots(figsize = (10, 6))
sns.boxplot(x="libellé columns", y="value", data=pd.melt(df[vols_specific_column]))
ax.set_xticklabels(ax.get_xticklabels(), rotation=45, horizontalalignment='right');
fig, ax = plt.subplots(figsize = (10, 6))
sns.lineplot(data=df[vols_specific_column], ax=ax, ci=int, linewidth=1);
fig, ax = plt.subplots(figsize = (10, 6))
sns.lineplot(data=df.loc['2019':'2016',vols_specific_column], ax=ax, ci=int, linewidth=1);
Les vols dits spécifiques représentent la plus grande quantité de cas enregistrés en France. Ici nous allons rester assez simpliste. Il existe une tendance décroissante du nombre de vols à la roulotte, à l'inverse les vols à la tire tendent à augmenter depuis 2012.
vols_violents_column = df.filter(regex=(".*Vols violents.*")).columns.values
fig, ax = plt.subplots(figsize = (10, 6))
sns.boxplot(x="libellé columns", y="value", data=pd.melt(df[vols_violents_column]))
ax.set_xticklabels(ax.get_xticklabels(), rotation=45, horizontalalignment='right');
fig, ax = plt.subplots(figsize = (10, 6))
sns.lineplot(data=df[vols_violents_column], ax=ax, ci=int, linewidth=1);
fig, ax = plt.subplots(figsize = (10, 6))
sns.lineplot(data=df.loc['2019':'2018',vols_violents_column], ax=ax, ci=int, linewidth=1);
vols_violents_column
Ce sous-axe peut être séparé en deux ensembles. D'un côté les "Vols violents sans arme contre des femmes sur voie publique ou autre lieu public" avec les "Vols violents sans arme contre d'autres victimes" qui représenter la majorité des données de cette analyse et de l'autre les "Vols violents sans arme contre des établissements financiers, commerciaux ou industriels" avec les "Vols violents sans arme contre des particuliers à leur domicile" qui sont pratiquement inexistant en comparaison.
vols_simple_column = df.filter(regex=(".*simple.*")).columns.values
fig, ax = plt.subplots(figsize = (10, 6))
sns.boxplot(x="libellé columns", y="value", data=pd.melt(df[vols_simple_column]))
ax.set_xticklabels(ax.get_xticklabels(), rotation=45, horizontalalignment='right');
fig, ax = plt.subplots(figsize = (10, 6))
sns.lineplot(data=df[vols_simple_column], ax=ax, ci=int, linewidth=1);
fig, ax = plt.subplots(figsize = (10, 6))
sns.lineplot(data=df.loc['2019':'2014',vols_simple_column], ax=ax, ci=int, linewidth=1);
Nous pouvons remarquer que la quantité de données liées aux activités 'Vols simples sur chantier', 'Vols simples sur exploitations agricoles' et 'Autres vols simples contre des établissements publics ou privés' sont assez restreintes en comparaison des 'Autres vols simples contre des particuliers dans deslocaux privés' et des 'Autres vols simples contre des particuliers dans des locaux ou lieux publics'. Aussi nous allons les regrouper pour plus de simpliciter.
vols_simple_column
df_vols_simple_column = df.loc[:,vols_simple_column]
other_steal_2_columns = [
'Vols simples sur chantier',
'Vols simples sur exploitations agricoles',
'Autres vols simples contre des établissements publics ou privés'
]
df_vols_simple_column["Autre"] = df_vols_simple_column[other_steal_2_columns].sum(axis=1)
df_vols_simple_column = df_vols_simple_column.drop(columns=other_steal_2_columns)
fig, ax = plt.subplots(figsize = (8, 5))
sns.boxplot(x="libellé columns", y="value", data=pd.melt(df_vols_simple_column));
ax.set_xticklabels(ax.get_xticklabels(), rotation=45, horizontalalignment='right');
fig, ax = plt.subplots(figsize = (10, 6))
sns.lineplot(data=df_vols_simple_column, ax=ax, ci=int, linewidth=1);
Ce regroupement nous permet de visualiser des périodicités sur l'ensemble des actes de ce sous-axe.
vols_vehicules_column = df.filter(regex=("Vols.*véhicule.*")).columns.values
fig, ax = plt.subplots(figsize = (10, 6))
sns.boxplot(x="libellé columns", y="value", data=pd.melt(df[vols_vehicules_column]))
ax.set_xticklabels(ax.get_xticklabels(), rotation=45, horizontalalignment='right');
fig, ax = plt.subplots(figsize = (10, 6))
sns.lineplot(data=df[vols_vehicules_column], ax=ax, ci=int, linewidth=1);
fig, ax = plt.subplots(figsize = (10, 6))
sns.lineplot(data=df.loc['2019':'2018',vols_vehicules_column], ax=ax, ci=int, linewidth=1);
Ce dernier sous axe montre que le nombre de vols de véhicule motorisés à 2 roues et de transport avec frêt tend à diminuer.
Reprenons le jeu de donnée initial. Nous allons opérer en trois temps :
Commençons par importer les librairies nécessaires à ce premier point
import numpy as np
import pandas as pd
from sklearn.preprocessing import OneHotEncoder
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, mean_squared_error, mean_squared_log_error
from sklearn.linear_model import LinearRegression
from sklearn.tree import DecisionTreeRegressor
Ensuite, définissons les variables nécessaires à notre cas d’étude. Elles vont permettre de préparer les données d’entré des différents modèles et de réaliser par la suite un train test split. Nous allons aussi réaliser une étape de prétransformation (ici une seule de catégorisation). L’objectif par cette étape est de considérer toutes les valeurs possibles des différentes dimensions d’input comme étant elle-même des dimensions.
########################################
# Define input & array columns
input_columns = ['departement','year','month']
array_input_columns = np.asarray(input_columns)
########################################
# Case to study
cambriolage_columns = df.filter(regex=(".*[Cc]ambriolage?s.*")).columns.values
champs_to_predict = "Total_Cambriolage"
column_to_use = np.concatenate((cambriolage_columns, array_input_columns), axis=None)
##################################################
# Préparation des données d'input
df_ml = df_all.loc[:,column_to_use]
df_ml[champs_to_predict] = df_ml[cambriolage_columns].sum(axis=1)
df_ml = df_ml.drop(columns=cambriolage_columns)
df_ml = df_ml['2018':'2019']
df_ml = df_ml.reset_index()
values_to_drop = [champs_to_predict,'date']
X = df_ml.drop(labels = values_to_drop, axis=1)
y = df_ml.loc[:,champs_to_predict]
##################################################
# No pipeline, only pre_processor categorical
enc = OneHotEncoder()
enc.fit(X)
X = enc.transform(X).toarray()
#########################
# Train & Test
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
y_test = pd.Series(y_test, dtype='float')
Les données sont prêtes à être exploitées par des modèles d’apprentissage. Dès l’obtention des prédictions, nous pourrons comparer les résultats sur ce modèle d’apprentissage. Pour calculer évaluer l’impact de se modèle, nous allons calculer deux variables : le RMSE et le pourcentage d’erreur moyen relatif aux données réelles. Pour ces deux mesures, l’objectif est d’avoir la plus petite valeur possible.
##################################################
# Apprentissage
cart_ml = DecisionTreeRegressor()
reg_ml = LinearRegression()
cart_ml.fit(X_train, y_train)
reg_ml.fit(X_train, y_train)
#########################
# prediction
y_test_pred_1 = cart_ml.predict(X_test)
y_test_pred_2 = reg_ml.predict(X_test)
#########################
# Output Test
X_test = enc.inverse_transform(X_test)
# DTR
df_ml_res_test_1 = pd.DataFrame(
data = X_test,
columns = input_columns
)
df_ml_res_test_1["Valeur_Reel"] = y_test.values
df_ml_res_test_1["Valeur_Predict"] = y_test_pred_1
df_ml_res_test_1["Difference_abs"] = abs(df_ml_res_test_1["Valeur_Reel"]-df_ml_res_test_1["Valeur_Predict"])
df_ml_res_test_1["%_Error"] = df_ml_res_test_1["Difference_abs"]/df_ml_res_test_1["Valeur_Reel"]
# LR
df_ml_res_test_2 = pd.DataFrame(
data = X_test,
columns = input_columns
)
df_ml_res_test_2["Valeur_Reel"] = y_test.values
df_ml_res_test_2["Valeur_Predict"] = y_test_pred_2
df_ml_res_test_2["Difference_abs"] = abs(df_ml_res_test_2["Valeur_Reel"]-df_ml_res_test_2["Valeur_Predict"])
df_ml_res_test_2["%_Error"] = df_ml_res_test_2["Difference_abs"]/df_ml_res_test_2["Valeur_Reel"]
#########################
# Résultats
df_moy_1 = df_ml_res_test_1[df_ml_res_test_1["Valeur_Reel"] != 0]
Err_Moy_Test_1 = round(df_moy_1["%_Error"].mean(),3)*100
df_moy_2 = df_ml_res_test_2[df_ml_res_test_2["Valeur_Reel"] != 0]
Err_Moy_Test_2 = round(df_moy_2["%_Error"].mean(),3)*100
RMSE_test_1 = np.sqrt(mean_squared_error(y_test, y_test_pred_1))
RMSE_test_2 = np.sqrt(mean_squared_error(y_test, y_test_pred_2))
#########################
# Affichage
print("RMSE test 1 : " + str(RMSE_test_1))
print("% Err Moy effectif test 1 : " + str(Err_Moy_Test_1) + " %")
print("RMSE test 2 : " + str(RMSE_test_2))
print("% Err Moy effectif test 2 : " + str(Err_Moy_Test_2) + " %")
En regardant ces résultats, nous pouvons tout de suite distinguer une grande disparité entre le RMSE et le pourcentage d’erreur moyen relatif. Si nous nous focalisons sur le RMSE, nous pouvons supposer que le test 2 est plus performant. À contrario, en se focalisant sur le pourcentage d’erreurs moyen effectif, ce serait le test 1.
En effet, si les deux modèles étaient relativement égaux (en termes de performance et de variation), les deux RMSE et les pourcentages d’erreurs moyennes effectifs aurait été proches l’un de l’autre. Or, ici le pourcentage d’erreurs moyen effectif (que l’on pourrait nommer l’erreur « réel ») est bien inférieur pour le premier test.
Cet aspect est totalement compréhensible. En effet, les modèles disposent en entrée d’une grande variété de valeurs (et par conséquent possiblement d’échelle). L’objectif est de minimiser l’erreur globale indépendamment des résultats. Pour pouvoir tenir compte au même poids des différences d’échelle, la mesure RMSE n’est pas la plus optimale et il faut privilégier le RMSLE.
En réalité l’utilisation du RMSLE permet uniquement de minimiser les erreurs des données d’eu aux différentes échelles ainsi que celle outliers. Dans une comparaison entre deux modèles distincts, le RMSE peut être suffisant. Cependant, ici nous allons comparer potentiellement n modèle, avec n préprocesseur, dans n cas. Le RMSE va donc vite être limité !
Remarque : avec le RMSLE il ne faut pas avoir de valeurs négatives et surtout n’avoir aucune valeur inférieure à 1. Compte tenu des potentiels résultats prédits, nous allons avoir deux manières de calculer le RMSLE : avec le log et le log + 1. Les résultats doivent théoriquement renvoyer le même résultat, sauf si une partie des données prédites se trouvent être entre – l’infinie et 0. Ainsi nous privilégierons le meilleur résultat parmi ces deux méthodes 😉
ytest = y_test
ypred = y_test_pred_1
print("RMSLE 1 :")
print(np.sqrt(mean_squared_log_error(ypred, ytest)))
print(np.sqrt(mean_squared_log_error(np.abs(ypred), np.abs(ytest))))
print(np.sqrt(np.mean((np.log1p(ypred, out=None) - np.log1p(ytest, out=None))**2)))
print("RMSE 1 :")
print(np.sqrt(mean_squared_error(np.abs(ypred), np.abs(ytest))))
ypred = y_test_pred_2
print("RMSLE 2 :")
print(np.sqrt(mean_squared_log_error(np.abs(ypred), np.abs(ytest))))
print(np.sqrt(np.mean((np.log1p(ypred, out=None) - np.log1p(ytest, out=None))**2)))
print("RMSE 2 :")
print(np.sqrt(mean_squared_error(np.abs(ypred), np.abs(ytest))))
Les résultats ici sont sans appel : le modèle du test 1 est bien plus performant que celui du test 2 et cela malgré à taux qui semblait être meilleur pour le RMSE. Nous pouvons aussi visualiser les tableaux associés aux résultats du test 1. Une seule chose à dire, c’est bien pour un premier jet.
df_moy_1.head(6)
df_moy_2.head(6)
Maintenant que la première partie est terminée et que les grandes étapes pour « ce modèle » ont été définies, nous pouvons mettre en place la base d’un processus de prédiction type « industriel ». Pour cela il va falloir « automatiser » toutes les « étapes » précédentes. Commençons d’abord par importer les modules nécessaires.
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, Normalizer, OneHotEncoder
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor
from sklearn.tree import DecisionTreeRegressor
from sklearn.svm import SVR
from joblib import dump, load
Définissons ensuite les variables nécessaires à notre processus avec notamment :
########################################
# Define input & array columns
input_columns = ['departement','year','month']
array_input_columns = np.asarray(input_columns)
########################################
# Define model regressor
cart_ml = DecisionTreeRegressor(criterion='mse')
rf_ml = RandomForestRegressor()
reg_ml = LinearRegression()
svr_ml = SVR()
models = [
{
'name':'cart',
'model':cart_ml
},
{
'name':'random forest',
'model':rf_ml
},
{
'name':'regression linéaire',
'model':reg_ml
},
{
'name':'SVR',
'model':svr_ml
}
]
########################################
# Préparation du tableau de score
columns = [
'Dictionnaire',
'ValeurPredict',
'PreProcessing',
'SCORING_Type',
'Model',
'SCORE_Test',
'Err_Moy_Test',
'SCORE_Val',
'Err_Moy_Val'
]
data = {
'Dictionnaire':["Nom du dictionnaire utilisé"],
'ValeurPredict':["Nom de la variable"],
'PreProcessing':["Pré-processus"],
'SCORING_Type':['Nom du type de score'],
'Model':["Nom du model"],
'SCORE_Test':["0"],
'Err_Moy_Test':["0"],
'SCORE_Val':["0"],
'Err_Moy_Val':["0"]
}
df_result = pd.DataFrame(data = data, columns = columns)
df_result = df_result.iloc[1:]
df_result['SCORE_Test'] = df_result['SCORE_Test'].astype(float)
df_result['Err_Moy_Test'] = df_result['Err_Moy_Test'].astype(float)
df_result['SCORE_Val'] = df_result['SCORE_Val'].astype(float)
df_result['Err_Moy_Val'] = df_result['Err_Moy_Val'].astype(float)
########################################
# Dictionnaire
####################
### Cambriolage
cambriolage_columns = df.filter(regex=(".*[Cc]ambriolage?s.*")).columns.values
other_cambriolage_columns = [
'Cambriolages de résidences secondaires',
'Cambriolages de locaux industriels, commerciaux ou financiers',
"Cambriolages d'autres lieux"
]
# Dictionnaire
cambriolage_case = {
'column_to_use':[
np.concatenate((cambriolage_columns, array_input_columns), axis=None),
np.concatenate(("Cambriolages de locaux d'habitations principales", array_input_columns), axis=None),
np.concatenate((other_cambriolage_columns, array_input_columns), axis=None)
],
'case':[cambriolage_columns,None,other_cambriolage_columns],
'champs_to_predict':["Total_Cambriolage","Cambriolages de locaux d'habitations principales","Autre"],
'date':('2018','2019'),
'name':'cambriolage_case'
}
####################
# Homicide et tentative
# 2018 2019
# Homicide séparation
all_homicides_columns = df.filter(regex=(".*[Hh]omicide?s.*")).columns.values
only_homicides_columns = df.filter(regex=(".*Homicides.*")).columns.values
only_tentative_homicides_columns = df.filter(regex=(".*homicides.*")).columns.values
murder_case = {
'column_to_use':[
np.concatenate((only_homicides_columns, array_input_columns), axis=None),
np.concatenate((only_tentative_homicides_columns, array_input_columns), axis=None)
],
'case':[only_homicides_columns,only_tentative_homicides_columns],
'champs_to_predict':["Total Homicide","Total tenta Homicide"],
'date':('2018','2019'),
'name':'murder_case'
}
####################
# les vols :
# Vols à main (3 cas) 2017 2019
vols_with_hands_column = df.filter(regex=(".*Vols à main.*")).columns.values
other_steal_columns = [
'Vols à main armée contre des établissements financiers',
'Vols à main armée contre des entreprises de transports de fonds',
'Vols à main armée contre des particuliers à leur domicile'
]
theft_with_hand_case = {
'column_to_use':[
np.concatenate((vols_with_hands_column, array_input_columns), axis=None),
np.concatenate(("Vols à main armée contre des éts industriels ou commerciaux", array_input_columns), axis=None),
np.concatenate((other_steal_columns, array_input_columns), axis=None)
],
'case':[vols_with_hands_column,None,other_steal_columns],
'champs_to_predict':["Total vol main","Vols à main armée contre des éts industriels ou commerciaux","Autre Vol Main"],
'date':('2018','2019'),
'name':'theft_with_hand_case'
}
# Vols spécifique (3 cas) 2016 2019
vols_specific_column = ['Vols à la tire', "Vols à l'étalage", 'Vols à la roulotte']
specific_theft_case = {
'column_to_use':[
np.concatenate(('Vols à la tire', array_input_columns), axis=None),
np.concatenate(("Vols à l'étalage", array_input_columns), axis=None),
np.concatenate(('Vols à la roulotte', array_input_columns), axis=None)
],
'case':[None,None,None],
'champs_to_predict':['Vols à la tire',"Vols à l'étalage",'Vols à la roulotte'],
'date':('2016','2019'),
'name':'specific_theft_case'
}
# Vols violent (1 cas) 2018 2019
vols_violents_column = df.filter(regex=(".*Vols violents.*")).columns.values
violent_theft_case = {
'column_to_use':[
np.concatenate((vols_violents_column, array_input_columns), axis=None)
],
'case':[vols_violents_column],
'champs_to_predict':["Total vol violent"],
'date':('2018','2019'),
'name':'violent_theft_case'
}
# Vols simple
vols_simple_column = df.filter(regex=(".*simple.*")).columns.values
other_steal_2_columns = [
'Vols simples sur chantier',
'Vols simples sur exploitations agricoles',
'Autres vols simples contre des établissements publics ou privés'
]
simple_theft_case = {
'column_to_use':[
np.concatenate((vols_simple_column, array_input_columns), axis=None),
np.concatenate(('Autres vols simples contre des particuliers dans deslocaux privés', array_input_columns), axis=None),
np.concatenate(('Autres vols simples contre des particuliers dans des locaux ou lieux publics', array_input_columns), axis=None),
np.concatenate((other_steal_2_columns, array_input_columns), axis=None),
],
'case':[vols_simple_column,None,None,other_steal_2_columns],
'champs_to_predict':[
"Total vol simple",
'Autres vols simples contre des particuliers dans deslocaux privés',
'Autres vols simples contre des particuliers dans des locaux ou lieux publics',
'Autre vols simple'
],
'date':('2016','2019'),
'name':'simple_theft_case'
}
Ensuite, fonctionnalisons tous points nécessaires aux traitements de ces données :
# prepare data input
def df_prepare_data(df_input, column_to_use, case, champs, **kwargs):
df = df_input.loc[:,column_to_use]
if case is not None:
df[champs] = df.loc[:,case].sum(axis=1)
df = df.loc[:].drop(columns=case)
if bool(kwargs.get('first')) or bool(kwargs.get('second')):
df = df.loc[kwargs.get('first'):kwargs.get('seconde')]
df = df.reset_index()
return df
def modeling_pre_processor(**kwargs):
preprocessor_case = kwargs.get('preprocessor_case')
if preprocessor_case == 0:
name = "SS year & OHE Dep, month"
numeric_features = ['year']
numeric_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler())
])
categorical_features = ['departement', 'month']
categorical_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='constant', fill_value='missing')),
('onehot', OneHotEncoder(handle_unknown='ignore'))
])
preprocessor = ColumnTransformer(
transformers = [
('num', numeric_transformer, numeric_features),
('cat', categorical_transformer, categorical_features)
])
elif preprocessor_case == 1:
name = "N year & OHE Dep, month"
numeric_features = ['year']
numeric_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='median')),
('scaler', Normalizer())
])
categorical_features = ['departement', 'month']
categorical_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='constant', fill_value='missing')),
('onehot', OneHotEncoder(handle_unknown='ignore'))
])
preprocessor = ColumnTransformer(
transformers = [
('num', numeric_transformer, numeric_features),
('cat', categorical_transformer, categorical_features)
])
elif preprocessor_case == 2:
name = "OHE year, Dep, month"
categorical_features = ['year','departement', 'month']
categorical_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='constant', fill_value='missing')),
('onehot', OneHotEncoder(handle_unknown='ignore'))
])
preprocessor = ColumnTransformer(
transformers = [
('cat', categorical_transformer, categorical_features)
])
return name, preprocessor
# Prediction resultats
def prediction_resultat(columns, X, Y_reel, Y_predict):
df = pd.DataFrame(
data = X,
columns = columns
)
df["Valeur_Reel"] = Y_reel
df["Valeur_Predict"] = Y_predict
df["Difference_abs"] = abs(df["Valeur_Reel"]-df["Valeur_Predict"])
df["%_Error"] = df["Difference_abs"]/df["Valeur_Reel"]
return df
# recupère la moyenne
def get_mean(df, isNull=True):
if isNull:
df = df.loc[df["Valeur_Reel"] != 0]
return df["%_Error"].mean()
else:
return df["%_Error"].mean()
# Methode de calculs des résultats RMSE
def score(ypred, ytest, score):
assert len(ytest) == len(ypred)
if score == "RMSLE_LOG1":
return np.sqrt(np.mean((np.log1p(ypred, out=None) - np.log1p(ytest, out=None))**2))
if score == "RMSLE":
return np.sqrt(mean_squared_log_error(np.abs(ypred), np.abs(ytest)))
# RMSLE
# np.sqrt(mean_squared_log_error(np.abs(ypred), np.abs(ytest)))
# np.sqrt(np.mean((np.log1p(ypred, out=None) - np.log1p(ytest, out=None))**2))
# RMSE
# np.sqrt(mean_squared_error(np.abs(ypred), np.abs(ytest)))
# Enregistrement du score
def scoring(df, **kwargs):
index = len(df.index)
df.loc[index,'Dictionnaire'] = kwargs.get('Dictionnaire')
df.loc[index,'ValeurPredict'] = kwargs.get('ValeurPredict')
df.loc[index,'PreProcessing'] = kwargs.get('PreProcessing')
df.loc[index,'Model'] = kwargs.get('Model')
df.loc[index,'SCORING_Type'] = kwargs.get('SCORING_Type')
df.loc[index,'SCORE_Test'] = kwargs.get('SCORE_Test')
df.loc[index,'Err_Moy_Test'] = kwargs.get('Err_Moy_Test')
df.loc[index,'SCORE_Val'] = kwargs.get('SCORE_Val')
df.loc[index,'Err_Moy_Val'] = kwargs.get('Err_Moy_Val')
return df
def run_dictionnary_case(df_main, df_result, dictionary_case, score_type, **kwargs):
isDisplay = kwargs.get('isDisplay')
if isDisplay:
print("Start dictionary case " + dictionary_case['name'])
for i in range(len(dictionary_case['column_to_use'])):
if isDisplay:
print("Case " + str(i+1) + "/" + str(len(dictionary_case['column_to_use'])) + " : " + dictionary_case['champs_to_predict'][i])
champs_to_predict = dictionary_case['champs_to_predict'][i]
##################################################
# Préparation des données d'input
df_ml = df_prepare_data(
df_input = df_main,
column_to_use = dictionary_case['column_to_use'][i],
champs = champs_to_predict,
case = dictionary_case['case'][i],
first = dictionary_case['date'][0],
second = dictionary_case['date'][1]
)
values_to_drop = [champs_to_predict,'date']
X = df_ml.drop(labels = values_to_drop, axis=1)
y = df_ml[champs_to_predict]
#########################
# Train & Test
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
y_test = pd.Series(y_test, dtype='int32')
#########################
# Validation
df_val = df_prepare_data(
df_input = df_main[df_main['year'] == 2020],
column_to_use = dictionary_case['column_to_use'][i],
champs = champs_to_predict,
case = dictionary_case['case'][i]
)
X_val = df_val.drop(labels = values_to_drop, axis=1)
y_val = df_val[champs_to_predict]
##################################################
# Modeling pre_processor pipeline
pre_processor_name, preprocessor = modeling_pre_processor(
preprocessor_case = kwargs.get('preprocessor_case')
)
##################################################
# Define current pipeline
for model in models:
model_processing = Pipeline(
steps = [
('preprocessor', preprocessor),
('classifier', model['model'])
])
model_processing.fit(X_train, y_train)
#########################
# Output modeling
filename = ''.join([
'resultat/model/',
dictionary_case['name'] + '_',
champs_to_predict + '_',
pre_processor_name + '_',
model['name'] + '_',
score_type + '_' + '.joblib'
])
filename = filename.replace(" ", "_")
dump(model_processing, filename)
#########################
# Prédiction test
y_test_pred = model_processing.predict(X_test)
df_ml_res_test = prediction_resultat(
columns = input_columns,
X = X_test,
Y_reel = y_test,
Y_predict = y_test_pred
)
#########################
# Prédiction val
y_val_pred = model_processing.predict(X_val)
df_ml_res_val = prediction_resultat(
columns = input_columns,
X = X_val,
Y_reel = y_val,
Y_predict = y_val_pred
)
#########################
# Enregistrement
score_test = score(y_test_pred, y_test, score=score_type)
Err_Moy_Test = (get_mean(df_ml_res_test)*100)
score_val = score(y_val_pred, y_val, score=score_type)
Err_Moy_Val = (get_mean(df_ml_res_val)*100)
#########################
# Enregistrement du score
df_result = scoring(
df = df_result,
Dictionnaire = dictionary_case['name'],
ValeurPredict = champs_to_predict,
PreProcessing = pre_processor_name,
Model = model['name'],
SCORING_Type = score_type,
SCORE_Test = score_test,
Err_Moy_Test = Err_Moy_Test,
SCORE_Val = score_val,
Err_Moy_Val = Err_Moy_Val
)
#########################
# Affichage
if isDisplay:
print("Model : " + model['name'])
print("\tScore test : " + str(score_test))
print("\t% Err Moy test : " + str(round(Err_Moy_Test,3)) + " %")
print("\tScore val : " + str(score_val))
print("\t% Err Moy val : " + str(round(Err_Moy_Val,3)) + " %")
if isDisplay:
print("\n")
Voici le code pour faire tourner la partie sur l’ensemble des modèles configurer dans le dictionnaire modèle.
Exemple 1 : run_dictionnary_case( df_main = df_all, df_result = df_result, dictionary_case = cambriolage_case, preprocessor_case = 2, score_type = "RMSLE", isDisplay = True )
Exemple 2 : run_dictionnary_case( df_main = df_all, df_result = df_result, dictionary_case = cambriolage_case, preprocessor_case = 2, score_type = "RMSLE_LOG1", isDisplay = False )
Remarque : nous n’allons pas l’exécuter, car nous ne voulons de score en doublon.
Remarque (2) : les modèles d’apprentissage choisi n’ont pas été configurés. En effet, ce travail sert de modèle d’évaluation et de prédiction sur le processus à suivre pour industrialiser une série de « champs à prédire ».
Par ailleurs, les couches complémentaires telles que la paramétrisation (par défaut, simple, use case), l’ajout des modèles ensembliste (parallèle, séquentielle), l’ajustage des données d’input / pre-processing, le grid search (hypertuning) sont des possibilités, des étapes qui peuvent être réalisé et bien entendu ajouter à ce cas d’usage. Toutefois, à la vue du faible nombre des dimensions, l’ajout de ces couches ne semble pas être pertinent. 😊 Bien entendu, en les ajoutant, nous pourrions (encore) améliorer les résultats de nos modèles 😊
import time
Case_to_train = [
cambriolage_case,
murder_case,
theft_with_hand_case,
specific_theft_case,
violent_theft_case,
simple_theft_case
]
score_type = ["RMSLE_LOG1","RMSLE"]
case = 0
tmp_global = 0
i = 1
for element in Case_to_train:
print("Dict num " + str(i) + " on " + str(len(Case_to_train)))
for j in range(3):
tmps1 = time.time()
print("\tpre processor " + str(j+1))
for k in score_type:
case += 1
run_dictionnary_case(
df_main = df_all,
df_result = df_result,
dictionary_case = element,
preprocessor_case = j,
score_type = k,
isDisplay = False
)
tmps2 = time.time()-tmps1
tmp_global = tmp_global + tmps2
print("\tTps exe prepro %f seconde(s)" %tmps2)
i = i + 1
tmp_global = tmp_global/60
print("tps execution global : %f min(s)" %tmp_global)
print("nb total treat : " + str(case))
Les dictionnaires ont été exécuté, bien entendu, pendant le déroulement du processus, des copies des modèles ont été réalisées en locales. Passons maintenant à l’évaluation de ces modèles.
Voici un aperçu du tableau des résultats.
print(df_result.shape)
df_result.head(13)
Maintenant rechercherons pour chaque valeur prédite quels sont les meilleurs résultats effectués sur l’année 2020.
Voici un exemple de condition possible :
acceptable_SCORE_value = 0.3 ERR_Moy = 30
condition_dict = (df_result['Dictionnaire'] == 'cambriolage_case')
condition_VP = (df_result['ValeurPredict'] == 'Total_Cambriolage')
condition_SCORE = (df_result['SCORE_Test'] < acceptable_SCORE_value) | (df_result['SCORE_Val'] < acceptable_SCORE_value)
condition_ERR = (df_result['Err_Moy_Test'] < ERR_Moy) | (df_result['Err_Moy_Val'] < ERR_Moy)
condition = condition_dict & condition_VP & condition_SCORE & condition_ERR
df_result.loc[condition]
Mais nous allons privilégier la simplicité 😊
best_scores = df_result.groupby(['Dictionnaire','ValeurPredict']).idxmin()['SCORE_Val']
resultats = df_result.loc[best_scores].reset_index().drop(columns=['index'])
resultats
Nous pouvons tout de suite remarquer que les modèles n’ont pas tous donné le même niveau de résultat. Par exemple, le nombre « total de cambriolage » ou les « Autres vols simples contrent des particuliers dans des locaux ou lieux publics » ont des prédictions très intéressantes. À l’inverse le nombre de vols à la tire lui est l’un des plus mauvais scores de cette synthèse. Mauvais score pouvant être justifié par le confinement.
Mise à jour : les premiers résultats (sur janvier à mars) étaient pas mal du tout, cependant lorsque j’ai repris ce projet, les évènements ont amoindri les résultats attendus. Bien entendu, cela a eu un impact sur nos prévisions, les rendant « moins » performantes.
resultats.loc[resultats['SCORE_Val'] < 0.4]
Maintenant que nos modèles sont définis, nous allons réaliser un peu de prévision. Nous allons reprendre la première prévision de l’année 2020 puis réaliser une seconde pour cet été (en espérant que le confinement soit terminé d’ici là !).
# Jeu de donnée input
x_values = []
departements = all_name_sheet
months = ['01','02','03','04','05','06','07','08']
year = '2020'
for dep in departements:
for month in months:
elem = [dep,year,month]
x_values.append(elem)
df_prediction = pd.DataFrame(
data = x_values,
columns = input_columns
)
# Chargement du modèle
dir_path = 'resultat/model/'
filename = dir_path + 'cambriolage_case_Total_Cambriolage_OHE_year,_Dep,_month_cart_RMSLE_.joblib'
model = load(filename)
# Reprise pre transfo
pre_processor_name, preprocessor = modeling_pre_processor(
preprocessor_case = 2
)
# Définition pipeline
model_processing = Pipeline(
steps = [
('preprocessor', preprocessor),
('classifier', model)
])
# Prévision
y_pred = model.predict(df_prediction)
df_prediction["Estimation du Total Cambriolage"] = y_pred
df_prediction
Focalisons-nous sur le département des Bouches-du-Rhône.
df_prediction.loc[df_prediction.departement == '13']
Regardons la différence en fonction des taux d’erreur moyens constatés entre prédiction et réel.
# Condition d'analyse
condition = (df_all['year']==2020) & (df_all['departement']=='13')
df_comp = df_all.loc[condition].set_index('month')
comparaison = df_prediction.loc[df_prediction.departement == '13'].set_index('month')
comparaison["Reel"] = df_comp.loc[:,cambriolage_columns].sum(axis=1)
comparaison["Difference"] = np.abs(comparaison["Estimation du Total Cambriolage"] - comparaison["Reel"])
taux_error = 0.13
cond_1 = (comparaison["Reel"] < round(comparaison["Estimation du Total Cambriolage"] * (1+taux_error)))
cond_2 = (comparaison["Reel"] > round(comparaison["Estimation du Total Cambriolage"] * (1-taux_error)))
condition = cond_1 & cond_2
comparaison["Résultat 1x tx err"] = condition
taux_error = 0.13 * 2
cond_1 = (comparaison["Reel"] < round(comparaison["Estimation du Total Cambriolage"] * (1+taux_error)))
cond_2 = (comparaison["Reel"] > round(comparaison["Estimation du Total Cambriolage"] * (1-taux_error)))
condition = cond_1 & cond_2
comparaison["Résultat 2x tx err"] = condition
comparaison["Intervalle de validation +"] = round(comparaison["Estimation du Total Cambriolage"] * (1+taux_error))
comparaison["Intervalle de validation -"] = round(comparaison["Estimation du Total Cambriolage"] * (1-taux_error))
comparaison
Malgré la situation actuelle (entrainant une baisse de performance), nous pouvons conclure que ces résultats sont satisfaisants 😊
Enfin, regardons les risques d’être touché par un cambriolage. Nous considérons que les risques d’être touché par un cambriolage sont identiques pour tous les habitants de chaque région. Que tous les habitants vivent dans des logements individuels et qu’une personne peut être victime que d’un unique cambriolage par ans (ces faits sont bien entendu faux, cela permet juste de simplifier le calcul). Aussi réalisons une simple division pour faire cette estimation.
nb_camb_13 = comparaison.iloc[5:8]['Estimation du Total Cambriolage'].sum(axis=0)
nb_pop_13 = df_population.loc[df_population['Departement'] == '13','Total'].values[0]
pourcentage_touche_par_camb = nb_camb_13 / nb_pop_13 * 100
print("Pourcentage d'être touché par un cambriolage cet été (pdv estimation) : " + str(round(pourcentage_touche_par_camb,2)) + " %")
0,21 % soit environ 1 chance sur 500. C’est quand même beaucoup même s'il ne s'agit que d'une estimation haute.
Force
Faiblesse
Les opportunités liées à ce travail pourraient être matérialisées par les prochaines étapes envisageables :
Menace : Aucune à ma connaissance
Je suis très content de ce travail. Toutefois il m’a demandé beaucoup de temps, notamment pour l’écriture des différents commentaires. J’ai consommé énormément d’énergie pour récupérer des jeux de données valides. Dans l’optique de me challenger, j’ai réalisé ce travail en parallèle des études, et du travail en entreprise. Ainsi je m’étais fixé comme date d’échéance fin juillet.
Malheureusement j’ai rapidement dû faire un choix quant aux jeux de données. Soit privilégier des données obscures avec davantage de potentiel biais ou favoriser la qualité au détriment de la variété. Mon choix s’est porté sur qualité afin de pouvoir plus rapidement démarrer des analyses.
Aujourd’hui, le domaine de la Data Science me fascine. Il est véritablement vaste, passionnant et je trouve épanouissant le cheminement menant à ce genre de réalisation. Toutefois, j’ai pu constater que beaucoup de personnes et d’entreprises ont du mal à percevoir la puissance qui peut se dégager de ce type de travaux. Peut-être par méconnaissance ou par réticence au changement ? Aussi par ce rapport, j’espère avoir permis de faciliter la compréhension et d’avoir donné l’opportunité à quelques personnes d’imaginer toutes les possibilités envisageables avec ces procédés.
Ce rapport est le premier travail que j’ai souhaité partager publiquement afin de montrer à quel point les modèles d’apprentissage automatique vulgairement appelé « intelligence artificielle » peuvent contribuer à répondre et résoudre des questions simples au premier abord qui peuvent tous nous toucher. J’espère avoir donné suffisamment de détail pour qu’il puisse être compris par tous.
Dans cette perspective, si vous êtes arrivé jusque-là, je serai ravie que vous poussiez vous aussi contribuer à ce travail en réalisant un retour. En effet, je pense qu’avoir les commentaires d’une communauté variée permettrait de mettre en lumière les points sur lesquelles je dois m’améliorer. Merci de votre attention ainsi qu’à Monsieur Adrien CHIFU, maitre de conférences en Science informatique de l’université Aix-Marseille pour les conseils avisés ainsi que la relecture.
Gaspard